#pragma once

#include <cv.h>
#include <highgui.h>
#include "../armed/time.h"
#include <vector>

struct RawPixel
{
	unsigned char r, g, b;

	RawPixel() : r(0), g(0), b(0) {}

	RawPixel(unsigned char R, unsigned char G, unsigned char B)
	{
		r = R;
		g = G;
		b = B;
	}

	bool operator == (RawPixel & other)	{
		return (r == other.r) && (g == other.g) && (b == other.b);
	}
	bool operator != (RawPixel & other)	{
		return (r != other.r) || (g != other.g) || (b != other.b);
	}
};

struct HSV
{
	unsigned char h, s, v;

	HSV() : h(0), s(0), v(0) {}

	HSV(unsigned char H, unsigned char S, unsigned char V)
	{
		h = H;
		s = S;
		v = V;
	}
};

struct Filter
{
	HSV top, bottom;

	Filter(HSV& t, HSV& b)
	{
		top = t;
		bottom = b;
	}

	bool check(HSV& pixel)
	{
		if( 
			(pixel.h < top.h) && (pixel.h > bottom.h) &&
			(pixel.s < top.s) && (pixel.s > bottom.s) &&
			(pixel.v < top.v) && (pixel.v > bottom.v)
			)
			return true;
		return false;
	}
};

class Object
{
public:
	RawPixel id;
	std::vector< Filter > filters;

	Object(RawPixel& myid)
	{
		id = myid;
	}

	void add_filter(Filter & filter)
	{
		filters.push_back(filter);
	}

	bool check(HSV& pixel)
	{
		for(unsigned int filter_index=0; filter_index < filters.size(); filter_index++)
		{
			Filter & filter = filters[filter_index];
			if( filter.check(pixel) ) return true;
		}
		return false;
	}
};

struct MouseEvent
{
	int triggered;
	int event, x, y, flags;
	RawPixel pixel;
	HSV converted;

	MouseEvent() : triggered(0), x(0), y(0), flags(0) {}
	MouseEvent(int event_, int x_, int y_, int flags_, 
		unsigned char r, unsigned char g, unsigned char b,
		unsigned char h, unsigned char s, unsigned char v)
	{
		triggered = 1;
		event = event_;
		x = x_;
		y = y_;
		flags = flags_;
		pixel.r = r;
		pixel.g = g;
		pixel.b = b;
	}
};

struct CameraResult
{
	double operation_interval;
	int status;
	CameraResult() : operation_interval(0), status(0) {}
};

class Camera
{
	CvCapture* capture;
	IplImage * image, * result, * imagehsv;
	MouseEvent event;

	std::vector< Object > objects;

	void mouse_event( int event_, int x, int y, int flags )
	{
		int index = (y * image->widthStep) + (x * image->nChannels);
		// BGR
		unsigned char b = image->imageData[index],
			g = image->imageData[index+1],
			r = image->imageData[index+2],
			h = imagehsv->imageData[index],
			s = imagehsv->imageData[index+1],
			v = imagehsv->imageData[index+2];

		event = MouseEvent(event_, x, y, flags, r, g, b, h, s, v);
		//printf("event: %d x: %d y: %d flags: %d \t\t r:%d g:%d b:%d\n",
		//	event_, x, y, flags, r, g, b );
	}

public:
	Time time;
	
	Camera(int camera_index)
	{
		image = NULL;
		capture = cvCaptureFromCAM( camera_index );
		if ( !capture ) {
			throw " cannot access camera device ";
		}
		cvNamedWindow("original", CV_WINDOW_AUTOSIZE);
		cvNamedWindow("result", CV_WINDOW_AUTOSIZE);
		cvSetMouseCallback("original", on_mouse, this);
		image = cvQueryFrame( capture );
		result = cvCreateImage( cvSize(image->width, image->height), 8, 3 );
		imagehsv = cvCreateImage( cvSize(160, 120), 8, 3 );
		/// 
		///printf(" + channel seq: %c%c%c%c\n", image->channelSeq[0], image->channelSeq[1], image->channelSeq[2], image->channelSeq[3]);
	}
	~Camera()
	{
		cvReleaseCapture( &capture );
		cvDestroyWindow("original");
		cvDestroyWindow("result");
		if(result) cvReleaseImage(&result);
		if(imagehsv) cvReleaseImage(&imagehsv);
	}

	void process()
	{
		image = cvQueryFrame( capture );

		//
		// filter color
		//
		cvResize(image, imagehsv, CV_INTER_LINEAR);
		cvCvtColor(imagehsv, imagehsv, CV_RGB2HSV);
		int max_ = imagehsv->imageSize;	// Image data size in bytes
										//(==image->height*image->widthStep
										//in case of interleaved data)
		for(int index=0; index<max_; index+=3)
		{
			HSV pixel( imagehsv->imageData[index], imagehsv->imageData[index+1], imagehsv->imageData[index+2] );
			for(unsigned int object_index=0; object_index<objects.size(); object_index++)
			{
				Object & object = objects[object_index];
				if( object.check(pixel) )
				{
					result->imageData[index] = object.id.b;
					result->imageData[index+1] = object.id.g;
					result->imageData[index+2] = object.id.r;
					break;
				}
				result->imageData[index] = 0;
				result->imageData[index+1] = 0;
				result->imageData[index+2] = 0;
			}
		}
		cvShowImage( "result", result );

		cvShowImage( "original", image );
	}

	void add_filter(RawPixel & object, Filter & filter)
	{
		// if object in objects:
		for(unsigned int i =0; i<objects.size(); i++)
		{
			Object & existing = objects[i];
			if( existing.id == object ) {
				existing.add_filter(filter);
				return;
			}
		}
		// else create new object and add filter
		Object novi(object);
		novi.add_filter(filter);
		objects.push_back( novi );
	}

	void clear_all_filters()
	{
		objects.clear();
	}

	MouseEvent get_event()
	{
		MouseEvent ret = event;
		event.triggered = false;
		return ret;
	}

	static void on_mouse( int event, int x, int y, int flags, void* param )
	{
		Camera * object = (Camera *) param;
		if (object)
			object->mouse_event(event, x, y, flags);
	}

	void find_objects()
	{
		/*		
		const int STEP = 10;
		unsigned char * rawdata = (unsigned char *) imagehsv->imageData;

		int y_increment = STEP * imagehsv->widthStep;
		int x_increment = STEP * imagehsv->nChannels;
		int index = 0;
		for(int y=STEP; y<imagehsv->height; y+=STEP)
		{
			index += y_increment;
			for(int x=STEP; x<imagehsv->width; x+=STEP)
			{
				index += x_increment;
				HSV pixel( rawdata[index], rawdata[index+1], rawdata[index+2] );
				// ...
			}
		}
		*/
	}
};










